package com.ittable.datasource.route.dynamicdatasource;

import com.ittable.datasource.route.dynamicdatasource.matcher.ReadStatementRuleMatcher;
import com.ittable.datasource.route.dynamicdatasource.matcher.WriteStatementRuleMatcher;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 采用 aspect 进行数据源转换
 *
 * @author srh
 * @date 2020/12/20
 **/
@Aspect
@Order(value = 1)
@Component
public class DynamicDataSourceHandlerAspect {

    @Pointcut(value = "execution(* org.mybatis.spring.SqlSessionTemplate.select*(..))")
    public void readPointcut() {
    }

    @Pointcut(value = "execution(* org.mybatis.spring.SqlSessionTemplate.insert(..))" +
            " || execution(* org.mybatis.spring.SqlSessionTemplate.update(..))" +
            "|| execution(* org.mybatis.spring.SqlSessionTemplate.delete(..))")
    public void writePointcut() {
    }

    @Around(value = "readPointcut()")
    public Object readAroundHandler(ProceedingJoinPoint point) throws Throwable {
        try {

            if (ReadStatementRuleMatcher.prefixMatch(getStatementName(point))) {
                System.out.println(String.format("%s %s",
                        "in@DynamicDataSourceHandlerAspect#readAroundHandler setReadDataSourceType success;",
                        String.format("className=%s, method=%s", getClassName(point), getMethodName(point))
                ));
                DynamicDataSourceContextHolder.setDataSourceType(DynamicDataSourceTypeEnum.READ);
            }

            return point.proceed();
        } catch (Throwable e) {
            System.err.println("err@DynamicDataSourceHandlerAspect#readAroundHandler failed; " + e.toString());
            throw e;
        } finally {
            System.out.println(String.format("%s %s",
                    "in@DynamicDataSourceHandlerAspect#readAroundHandler clearReadDataSourceType success;",
                    String.format("className=%s, method=%s", getClassName(point), getMethodName(point))
            ));
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    @Around(value = "writePointcut()")
    public Object writeAroundHandler(ProceedingJoinPoint point) throws Throwable {
        try {
            if (WriteStatementRuleMatcher.prefixMatch(getStatementName(point))) {
                System.out.println(String.format("%s %s",
                        "in@DynamicDataSourceHandlerAspect#writeAroundHandler setWriteDataSourceType success;",
                        String.format("className=%s, method=%s", getClassName(point), getMethodName(point))
                ));
                DynamicDataSourceContextHolder.setDataSourceType(DynamicDataSourceTypeEnum.WRITE);
            }

            return point.proceed();
        } catch (Throwable e) {
            System.err.println("err@DynamicDataSourceHandlerAspect#writeAroundHandler failed; " + e.toString());
            throw e;
        } finally {
            System.out.println(String.format("%s %s",
                    "in@DynamicDataSourceHandlerAspect#writeAroundHandler clearWriteDataSourceType success;",
                    String.format("className=%s, method=%s", getClassName(point), getMethodName(point))
            ));
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

    private <R extends JoinPoint> String getClassName(R point) {
        return point.getTarget().getClass().getName();
    }

    private <R extends JoinPoint> Method getMethod(R point) {
        return ((MethodSignature) point.getSignature()).getMethod();
    }

    private <R extends JoinPoint> String getMethodName(R point) {
        return getMethod(point).getName();
    }

    private <R extends JoinPoint> String getStatementName(R point) {
        return (String) point.getArgs()[0];
    }
}
